/*
 *  Copyright (C) 2009 Antonio Mata Chamorro
 *  
 *  Este programa es software libre: usted puede redistribuirlo y/o modificarlo
 *  bajo los t�rminos de la Licencia P�blica General GNU publicada
 *  por la Fundaci�n para el Software Libre, ya sea la versi�n 3
 *  de la Licencia, o (a su elecci�n) cualquier versi�n posterior.
 *  
 *  Este programa se distribuye con la esperanza de que sea �til, pero 
 *  SIN GARANT�A ALGUNA; ni siquiera la garant�a impl�cita
 *  MERCANTIL o de APTITUD PARA UN PROP�SITO DETERMINADO. 
 *  Consulte los detalles de la Licencia P�blica General GNU para obtener 
 *  una informaci�n m�s detallada. 
 *  
 *  Deber�a haber recibido una copia de la Licencia P�blica General GNU
 *  junto a este programa. 
 *  En caso contrario, consulte <http://www.gnu.org/licenses/>.
 *   
 */
package org.javahispano.javacup.tacticas_aceptadas.amata.com.amata.javacup.tacticas;

import java.util.ArrayList;
import java.util.List;

import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.Posicion;
import org.newdawn.slick.geom.Circle;
import org.newdawn.slick.geom.Line;

/**
 * Busca los mejores puntos a los que un jugador puede realizar un pase.
 * 
 * <p/>
 * 
 * Se considera que un punto es bueno para realizar un pase si esta a una
 * distancia m�nima (para que el pase no sea demasiado corto) y m�xima (para que
 * el pase pueda llegar) de jugador que controla la pelota. Tambi�n se tiene en
 * cuenta que los jugadores contrarios no puedan interceptar el pase y las
 * posibilidades de marcar gol despu�s del gol.
 * 
 * @author Antonio Mata Chamorro
 * */
public class SupportSpotCalculator implements SpotCalculator {
	private StrikeCalculator strikeCalculator;
	private List<Spot> spots = new ArrayList<Spot>();
	private float PLAYER_DIAMETER = 6f;
	private SoccerTeam team;
	private boolean findInPlayerSpot = false;

	private int verticalSlice = 6;
	private int horizontalSlice = 10;
	private float minPassDistance = (float) Constantes.DISTANCIA_CONTROL_BALON * 10;
	private float maxPassDistance = (float) Constantes.LARGO_CAMPO_JUEGO / 3;
	private float passDegradationWeight = 0.9f;
	private float defenderMinDistanceInPass = (float) Constantes.DISTANCIA_CONTROL_BALON * 3;

	public SupportSpotCalculator(SoccerTeam team,
			StrikeCalculator strikeCalculator) {
		this.team = team;
		this.strikeCalculator = strikeCalculator;

		// configuracion
		this.verticalSlice = team.getMatch().getConfiguration()
				.getVerticalSlice();
		this.horizontalSlice = team.getMatch().getConfiguration()
				.getHorizontalSlice();
		this.minPassDistance = team.getMatch().getConfiguration()
				.getMinPassDistance();
		this.maxPassDistance = team.getMatch().getConfiguration()
				.getMaxPassDistance();
		this.passDegradationWeight = team.getMatch().getConfiguration()
				.getPassDegradationWeight();
		this.defenderMinDistanceInPass = team.getMatch().getConfiguration()
				.getDefenderMinDistanceInPass();

		float deltaX = (float) Constantes.ANCHO_CAMPO_JUEGO / verticalSlice;
		float deltaY = (float) Constantes.LARGO_CAMPO_JUEGO / horizontalSlice;

		// construimos la lista de puntos
		for (int i = 1; i < verticalSlice / 2; i++) {
			for (int j = 1; j < horizontalSlice / 2; j++) {
				// cuadrante derecho bajo
				spots.add(new Spot(new Posicion(deltaX * i, deltaY * j)));
				// cuadrante izquierdo bajo
				spots.add(new Spot(new Posicion(-deltaX * i, deltaY * j)));
				// cuadrante derecho alto
				spots.add(new Spot(new Posicion(deltaX * i, -deltaY * j)));
				// cuadrante izquierdo alto
				spots.add(new Spot(new Posicion(-deltaX * i, -deltaY * j)));

			}
		}

	}

	/**
	 * Si {@link #isFindInPlayerSpot()} se eval�an como puntos de pase todas las
	 * posiciones de los jugadores propios. Si no se eval�an una matriz de
	 * puntos formada por {@link SoccerMatchConfiguration#getVerticalSlice()}
	 * divisiones verticales del campo de juego y
	 * {@link SoccerMatchConfiguration#getHorizontalSlice()} divisiones
	 * horizontales. Una vez escogidos los puntos se utiliza el m�todo
	 * {@link #eval�ateSpot()} para eval�ar los puntos.
	 * <p/>
	 * Despu�s de eval�ar todos estos puntos se almacenan en un
	 * {@link SpotResults} y se reordenan del siguiente modo:<br/>
	 * - el punto con la mejor puntuaci�n se mantiene el primero en el
	 * {@link SpotResults}<br/>
	 * - el resto de puntos se intercambian por otros de menor puntuaci�n si hay
	 * un jugador propio cercano a estos �ltimos
	 * 
	 * */
	public void findSpots(SoccerPlayer player, SpotResults results) {
		// recorremos todos los puntos
		if (this.findInPlayerSpot) {
			// recorremos la posicion de todos los jugadores
			Spot s = new Spot(new Posicion(0, 0));
			for (SoccerPlayer p : this.team.getPlayers()) {
				s.setPosicion(p.getPosicion());
				evaluateSpot(player, s, results);
			}

		} else {
			for (Spot s : spots) {
				evaluateSpot(player, s, results);
			}

		}
		results.sort();

		// a�adimos al attacker
		ArrayList<SoccerPlayer> supporters = new ArrayList<SoccerPlayer>();
		supporters.add(player.getTeam().findClosestPlayer(
				player.getTeam().getMatch().getBallPosicion()));

		// al mejor punto ahi que ir si o si
		if (results.getNumber() > 0) {
			supporters.add(player.getTeam().findClosestPlayer(
					results.getSpot(0).getPosicion()));
		}

		// reordenamos el resto de puntos para ir solo si tenemos alg�n jugador
		// cerca
		for (int i = 0; i < results.getNumber(); i++) {
			for (int j = i + 1; j < results.size(); j++) {
				if (!supporters.contains(player.getTeam().findClosestPlayer(
						results.getSpot(j).getPosicion()))) {
					supporters.add(player.getTeam().findClosestPlayer(
							results.getSpot(j).getPosicion()));
					results.swap(i, j);
					break;
				}
			}
		}

		// si no hay resultados devolvemos el punto nulo
		if (results.getNumber() == 0)
			results.addSpot(Spot.SPOT_NULL);
	}

	/**
	 * Si la distancia entre el jugador y el punto es menor de
	 * {@link SoccerMatchConfiguration#getMinPassDistance()} el pase se ignora.
	 * Si la distacia es mayor de
	 * {@link SoccerMatchConfiguration#getMaxPassDistance()} tambi�n se ignora.
	 * Tambi�n se desestiman todos los pases a los puntos que puedan ser
	 * interceptados por alg�n jugador contrario, asi como aquellos en los que
	 * un contrario pueda controlar la pelota despu�s del pase.
	 * <p/>
	 * Los pases restantes son puntuados seg�n la posibilides que
	 * {@link StrikeCalculator} calcula para que un tiro desde esa posicion
	 * marque gol multiplicado por el factor
	 * {@link SoccerMatchConfiguration#getPassDegradationWeight()}
	 * */
	protected void evaluateSpot(SoccerPlayer player, Spot s, SpotResults results) {
		// para cada punto vemos si estamos dentro de un rango de distancia,
		// desestimamos los que estan muy cerca o muy lejos
		if (minPassDistance > player.getPosicion().distancia(s.getPosicion())
				|| player.getPosicion().distancia(s.getPosicion()) > maxPassDistance)
			return;
		// para cada punto comprobamos las posibilidades de pasar
		if (!isPassSafe(player, s))
			return;
		// para cada punto calculamos las posibilidades de marcar con
		// strikeCalculator
		this.strikeCalculator.findKick(player, s);
		// para cada punto calculamos la distancia m�nima a la que se encuentra
		// un oponente
		double distanceToPlayer = Constantes.LARGO_CAMPO;
		for (SoccerPlayer p : player.getTeam().getOppositeTeam().getPlayers()) {
			distanceToPlayer = Math.min(distanceToPlayer, p.getPosicion()
					.distancia(player.getPosicion()));
		}
		// 0 si alg�n defensor estan dentro de la distancia de control y 1 si
		// cualquier defensor estan por encima de defenderMinDistance
		float factor = distanceToPlayer < Constantes.DISTANCIA_CONTROL_BALON
				|| distanceToPlayer < defenderMinDistanceInPass ? 0 : 1;

		s.setValue(s.getValue() * factor * passDegradationWeight);
		results.addSpot(s);

	}

	/**
	 * Calcula si un pase desde un jugador a un punto puede ser interceptado por
	 * alg�n jugador contrario. Primero se calcula el tiempo que la peloto
	 * tardara en llegar al punto de pase. Despu�s se comprueba si alg�n jugador
	 * contrario puede recorrer en ese tiempo una distancia como para llegar al
	 * punto de pase. Por �ltimo se comprueba la linea que traza el pase
	 * intersecciona con alg�n circulo que ocupe la posicion de los jugadores
	 * contrarios.
	 * 
	 * @param player
	 *            jugador que realiza el pase
	 * @param spot
	 *            punto al que se trata de pasar la pelota
	 * @return true el pase es posible, false el pase sera interceptado
	 * */
	protected boolean isPassSafe(SoccerPlayer player, Spot spot) {

		Line line = new Line((float) player.getPosicion().getX(),
				(float) player.getPosicion().getY(), (float) spot.getPosicion()
						.getX(), (float) spot.getPosicion().getY());

		// calcular el tiempo que la pelota tarda en recorrer esa distancia
		double v = player.getPassForce()
				* Constantes
						.getVelocidadRemate(player.getDetalle().getRemate());
		double s = player.getPosicion().distancia(spot.getPosicion());

		// t = s / v;
		float t = (float) (s / v);

		for (SoccerPlayer p : player.getTeam().getOppositeTeam().getPlayers()) {
			/*
			 * si el jugador tiene que recorrer mas espacio que la pelota
			 * consideramos que no llega, asumimos que la pelota siempre se
			 * mueve mas deprisa que cualquier jugador
			 */
			double s1 = p.getPosicion().distancia(spot.getPosicion());
			if (s1 > s)
				continue;
			double s2 = p.getPosicion().distancia(player.getPosicion());
			if (s2 > s)
				continue;
			/*
			 * distancia que recorrer el jugador contrario en el tiempo que la
			 * pelota tarda en recorrer su espacio
			 */
			float d1 = (float) Constantes.getVelocidad(p.getDetalle()
					.getVelocidad())
					* t;

			if (Math.pow(Math.max(s1, s2), 2) > Math.pow(d1, 2))
				continue;

			Circle circle = new Circle((float) p.getPosicion().getX(),
					(float) p.getPosicion().getY(), PLAYER_DIAMETER);
			if (circle.intersects(line))
				return false;
		}

		return true;
	}

	/**
	 * @return true si tratamos de buscar los mejores punto de pase entre la
	 *         posicion que ocupan los jugadores propios actualmente
	 * */
	public boolean isFindInPlayerSpot() {
		return findInPlayerSpot;
	}

	/**
	 * Permite configurar que puntos se escogeran para eval�arlos como puntos de
	 * pase
	 * */
	public void setFindInPlayerSpot(boolean findInPlayerSpot) {
		this.findInPlayerSpot = findInPlayerSpot;
	}
}
